#include "wx/wxprec.h"
#include "wx/init.h"
#include "wx/ptr_scpd.h"
#include "wx/except.h"

#if defined(__WXMSW__) && defined(__WXDEBUG__)

static struct EnableMemLeakChecking {
  EnableMemLeakChecking() {
    wxCrtSetDbgFlag( _CRTDBG_LEAK_CHECK_DF );
  }
} gs_enableLeakChecks;
#endif // __WXMSW__ && __WXDEBUG__

class wxDummyConsoleApp : public wxAppConsole {
  public:
    wxDummyConsoleApp() { }

    virtual int OnRun() { wxFAIL_MSG( _T( "unreachable code" ) ); return 0; }

    DECLARE_NO_COPY_CLASS( wxDummyConsoleApp )
};

wxDECLARE_SCOPED_PTR( wxAppConsole, wxAppPtrBase )
wxDEFINE_SCOPED_PTR( wxAppConsole, wxAppPtrBase )

class wxAppPtr : public wxAppPtrBase {
  public:
    wxEXPLICIT wxAppPtr( wxAppConsole *ptr = NULL ) : wxAppPtrBase( ptr ) { }
    ~wxAppPtr() {
      if( get() ) {
        wxApp::SetInstance( NULL );
      }
    }

    void Set( wxAppConsole *ptr ) {
      reset( ptr );
      wxApp::SetInstance( ptr );
    }

    DECLARE_NO_COPY_CLASS( wxAppPtr )
};

class wxCallAppCleanup {
  public:
    wxCallAppCleanup( wxAppConsole *app ) : m_app( app ) { }
    ~wxCallAppCleanup() { if( m_app ) m_app->CleanUp(); }

    void Dismiss() { m_app = NULL; }

  private:
    wxAppConsole *m_app;
};

static inline void Use( void * ) { }

#define WX_SUPPRESS_UNUSED_WARN(x) Use(&x)

static struct InitData {
  InitData() {
    nInitCount = 0;
    argc = 0;
  }

  wxCRIT_SECT_DECLARE_MEMBER( csInit );
  size_t nInitCount;
  int argc;
  wchar_t **argv;
  DECLARE_NO_COPY_CLASS( InitData )
} gs_initData;

static void ConvertArgsToUnicode( int argc, char **argv ) {
  gs_initData.argv = new wchar_t *[argc + 1];
  int wargc = 0;
  for( int i = 0; i < argc; i++ ) {
    #ifdef __DARWIN__
    wxWCharBuffer buf( wxConvFileName->cMB2WX( argv[i] ) );
    #else
    wxWCharBuffer buf( wxConvLocal.cMB2WX( argv[i] ) );
    #endif
    if( !buf ) {
      wxLogWarning( _( "Command line argument %d couldn't be converted to Unicode and will be ignored." ),
                    i );
    } else // converted ok
    { gs_initData.argv[wargc++] = wxStrdup( buf ); }
  }
  gs_initData.argc = wargc;
  gs_initData.argv[wargc] = NULL;
}

static void FreeConvertedArgs() {
  if( gs_initData.argv ) {
    for( int i = 0; i < gs_initData.argc; i++ ) {
      free( gs_initData.argv[i] );
    }
    delete [] gs_initData.argv;
    gs_initData.argv = NULL;
    gs_initData.argc = 0;
  }
}

static bool DoCommonPreInit() {
  #if wxUSE_LOG
  // Reset logging in case we were cleaned up and are being reinitialized.
  wxLog::DoCreateOnDemand();
  delete wxLog::SetActiveTarget( new wxLogBuffer );
  #endif // wxUSE_LOG
  return true;
}

// non customizable initialization done after wxApp creation and initialization
static bool DoCommonPostInit() {
  wxModule::RegisterModules();
  if( !wxModule::InitializeModules() ) {
    wxLogError( _( "Initialization failed in post init, aborting." ) );
    return false;
  }
  return true;
}

bool wxEntryStart( int& argc, wxChar **argv ) {
  if( !DoCommonPreInit() ) {
    return false;
  }
  wxAppPtr app( wxTheApp );
  if( !app.get() ) {
    wxAppInitializerFunction fnCreate = wxApp::GetInitializerFunction();
    if( fnCreate ) {
      app.Set( ( *fnCreate )() );
    }
  }
  if( !app.get() ) {
    app.Set( new wxDummyConsoleApp );
  }
  if( !app->Initialize( argc, argv ) ) {
    return false;
  }
  wxCallAppCleanup callAppCleanup( app.get() );
  if( !app->OnInitGui() ) {
    return false;
  }
  if( !DoCommonPostInit() ) {
    return false;
  }
  app.release();
  callAppCleanup.Dismiss();
  #if wxUSE_LOG
  delete wxLog::SetActiveTarget( NULL );
  #endif // wxUSE_LOG
  return true;
}

bool wxEntryStart( int& argc, char **argv ) {
  ConvertArgsToUnicode( argc, argv );
  if( !wxEntryStart( gs_initData.argc, gs_initData.argv ) ) {
    FreeConvertedArgs();
    return false;
  }
  return true;
}

static void DoCommonPreCleanup() {
  #if wxUSE_LOG
  wxLog::DontCreateOnDemand();
  // this will flush the old messages if any
  delete wxLog::SetActiveTarget( new wxLogStderr );
  #endif // wxUSE_LOG
}

// cleanup done after destroying wxTheApp
static void DoCommonPostCleanup() {
  wxModule::CleanUpModules();
  FreeConvertedArgs();
  delete wxMessageOutput::Set( NULL );
  #if wxUSE_LOG
  // and now delete the last logger as well
  delete wxLog::SetActiveTarget( NULL );
  #endif // wxUSE_LOG
}

void wxEntryCleanup() {
  DoCommonPreCleanup();
  if( wxTheApp ) {
    wxTheApp->CleanUp();
    wxAppConsole * const app = wxApp::GetInstance();
    wxApp::SetInstance( NULL );
    delete app;
  }
  DoCommonPostCleanup();
}

#ifndef __WXMSW__
#define wxEntryReal wxEntry
#endif // !__WXMSW__

int wxEntryReal( int& argc, wxChar **argv ) {
  wxInitializer initializer( argc, argv );
  if( !initializer.IsOk() ) {
    #if wxUSE_LOG
    delete wxLog::SetActiveTarget( NULL );
    #endif
    return -1;
  }
  wxTRY {

    if( !wxTheApp->CallOnInit() ) {
      return -1;
    }

    class CallOnExit
    {
      public:
        ~CallOnExit() { wxTheApp->OnExit(); }
    } callOnExit;

    WX_SUPPRESS_UNUSED_WARN( callOnExit );

    return wxTheApp->OnRun();
  }
  wxCATCH_ALL( wxTheApp->OnUnhandledException(); return -1; )
}

int wxEntry( int& argc, char **argv ) {
  ConvertArgsToUnicode( argc, argv );
  return wxEntry( gs_initData.argc, gs_initData.argv );
}

bool wxInitialize( int argc, wxChar **argv ) {
  wxCRIT_SECT_LOCKER( lockInit, gs_initData.csInit );
  if( gs_initData.nInitCount++ ) {
    // already initialized
    return true;
  }
  return wxEntryStart( argc, argv );
}

bool wxInitialize( int argc, char **argv ) {
  wxCRIT_SECT_LOCKER( lockInit, gs_initData.csInit );
  if( gs_initData.nInitCount++ ) {
    return true;
  }
  return wxEntryStart( argc, argv );
}

void wxUninitialize() {
  wxCRIT_SECT_LOCKER( lockInit, gs_initData.csInit );
  if( --gs_initData.nInitCount == 0 ) {
    wxEntryCleanup();
  }
}
